﻿using OnDash.Mathematics.Vector;

namespace OnDash.Mathematics.Geometry;

public struct Box2(Vector2 min, Vector2 max) : IEquatable<Box2>
{
    public static readonly Box2 Empty = new(0, 0, 0, 0);

    public float Left { get; set; }

    public float Right { get; set; }

    public float Top { get; set; }

    public float Bottom { get; set; }

    private Vector2 _min = Vector2.ComponentMin(min, max);

    public Vector2 Min
    {
        get => _min;
        set
        {
            if (value.X > _max.X) _max.X = value.X;
            if (value.Y > _max.Y) _max.Y = value.Y;
            _min = value;
        }
    }

    private Vector2 _max = Vector2.ComponentMax(min, max);

    public Vector2 Max
    {
        get => _max;
        set
        {
            if (value.X < _min.X) _min.X = value.X;
            if (value.Y < _min.Y) _min.Y = value.Y;
            _max = value;
        }
    }

    public float Width => MathHelper.Abs(Right - Left);
    public float Height => MathHelper.Abs(Top - Bottom);

    public Box2(float minX, float minY, float maxX, float maxY)
        : this(new Vector2(minX, minY), new Vector2(maxX, maxY))
    {
    }

    public static Box2 FromTlrb(float top, float left, float right, float bottom) =>
        new(left, top, right, bottom);

    public static Box2 FromDimensions(float left, float top, float width, float height) =>
        new(left, top, left + width, top + height);

    public static Box2 FromDimensions(Vector2 position, Vector2 size) =>
        FromDimensions(position.X, position.Y, size.X, size.Y);

    public Vector2 HalfSize
    {
        get => Size / 2;
        set => Size = value * 2;
    }

    public Vector2 Center
    {
        get => HalfSize + _min;
        set => Translate(value - Center);
    }

    public Vector2 Size
    {
        get => Max - Min;
        set
        {
            var center = Center;
            _min = center - value * 0.5f;
            _max = center + value * 0.5f;
        }
    }

    public Box2 Translated(Vector2 distance)
    {
        var box = this;
        box.Translate(distance);
        return box;
    }

    public void Translate(Vector2 distance)
    {
        _min += distance;
        _max += distance;
    }

    public bool Contains(Vector2 point) => _min.X < point.X && point.X < _max.X && _min.Y < point.Y && point.Y < _max.Y;

    public bool ContainsInclusive(Vector2 point) =>
        _min.X <= point.X && point.X <= _max.X && _min.Y <= point.Y && point.Y <= _max.Y;

    public bool ContainsExclusive(Vector2 point) =>
        _min.X < point.X && point.X < _max.X && _min.Y < point.Y && point.Y < _max.Y;

    public bool Contains(Vector2 point, bool boundaryInclusive) =>
        boundaryInclusive ? ContainsInclusive(point) : ContainsExclusive(point);

    public bool Contains(Box2 other) =>
        _max.X >= other._min.X && _min.X <= other._max.X && _max.Y >= other._min.Y && _min.Y <= other._max.Y;

    public static Box2 Intersect(Box2 a, Box2 b)
    {
        var min = Vector2.ComponentMax(a.Min, b.Min);
        var max = Vector2.ComponentMin(a.Max, b.Max);

        if (max.X >= min.X && max.Y >= min.Y)
            return new Box2(min, max);
        return Empty;
    }

    public float DistanceToNearestEdge(Vector2 point)
    {
        var distX = new Vector2
        (
            MathHelper.Max(0f, MathHelper.Max(_min.X - point.X, point.X - _max.X)),
            MathHelper.Max(0f, MathHelper.Max(_min.Y - point.Y, point.Y - _max.Y))
        );
        return distX.Length;
    }

    public void Scale(Vector2 scale, Vector2 anchor)
    {
        _min = anchor + (_min - anchor) * scale;
        _max = anchor + (_max - anchor) * scale;
    }

    public Box2 Scaled(Vector2 scale, Vector2 anchor)
    {
        var box = this;
        box.Scale(scale, anchor);
        return box;
    }

    public void Inflate(Vector2 point)
    {
        _min = Vector2.ComponentMin(_min, point);
        _max = Vector2.ComponentMax(_max, point);
    }

    public Box2 Inflated(Vector2 point)
    {
        var box = this;
        box.Inflate(point);
        return box;
    }

    public static bool operator ==(Box2 left, Box2 right) => left.Equals(right);
    public static bool operator !=(Box2 left, Box2 right) => !left.Equals(right);

    public override bool Equals(object? obj) => obj is Box2 box2 && Equals(box2);

    public bool Equals(Box2 other)
    {
        return _min.Equals(other._min) &&
               _max.Equals(other._max);
    }

    public override int GetHashCode() => HashCode.Combine(_min, _max);

    public override string ToString() => $"{Min} - {Max}";
}